//
// Copyright (c) 2009 All Right Reserved
//
// vl
//
// 2009-01-01
// Contains ...
namespace LargoCommon.Music
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using JetBrains.Annotations;
///
/// Harmonic Analyzer.
///
public sealed class HarmonicAnalyzer
{
#region Fields
///
/// Musical Block.
///
private readonly MusicalBody musicalBody;
///
/// Harmonic motive number.
///
private int harMotiveNumber;
///
/// Covered bars by harmony.
///
private BitArray coveredBars;
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The given musical body.
public HarmonicAnalyzer(MusicalBody givenMusicalBody)
{
this.musicalBody = givenMusicalBody;
}
///
/// Initializes a new instance of the class.
///
[UsedImplicitly]
public HarmonicAnalyzer()
{
}
#endregion
#region Properties
///
/// Gets or sets Musical Form.
///
private HarmonicModel HarmonicModel { get; set; }
#endregion
#region Public methods - Harmonic Analyze
///
/// Extract Harmonical Motive.
///
/// The harmonic model.
/// Harmonic Analyze Type.
///
/// Returns value.
///
public IEnumerable AnalyzeHarmony(HarmonicModel harmonicModel, HarmonicAnalysisType analyzeType)
{
this.HarmonicModel = harmonicModel;
var harmonicStream = new HarmonicStream(harmonicModel.Header); //// "Derived from core"
//// this.HarmonicStreamAnalyzer.ExtractHarmonicStreamByTicks();
var changes = new List();
var lastModalityCode = string.Empty;
switch (analyzeType) {
case HarmonicAnalysisType.DivisionByTicks: {
foreach (var bar in this.musicalBody.Bars) {
var harmonicBar = bar.HarmonicBar;
if (harmonicBar == null) {
continue;
}
harmonicStream.HarmonicBars.Add(harmonicBar);
var code = harmonicBar.GetHarmonicModalityCode;
if (code == lastModalityCode) {
continue;
}
var tc = new TonalityChange(bar.BarNumber) { HarmonicModalityCode = code };
changes.Add(tc);
lastModalityCode = code;
}
var harmonicChanges = this.ExtractHarmonicalMotive(harmonicStream);
changes.AddRange(harmonicChanges);
}
break;
}
return changes;
}
///
/// Finds the repeated harmonic motive.
///
/// The harmonic stream.
/// Returns value.
private HarmonicChange FindRepeatedHarmonicMotive(HarmonicStream harmonicStream)
{
Contract.Requires(harmonicStream != null);
const int minMotiveLength = 3;
var numberOfBars = harmonicStream.HarmonicBars.Count;
for (var barIdx = 0; barIdx < numberOfBars; barIdx++) {
if (barIdx >= this.coveredBars.Length) {
continue;
}
if (this.coveredBars[barIdx]) {
continue;
}
var harmonicBar = harmonicStream.HarmonicBars.ElementAt(barIdx);
if (harmonicBar == null) {
continue;
}
for (var nextIdx = barIdx + 1; nextIdx < numberOfBars; nextIdx++) {
if (nextIdx >= this.coveredBars.Count) { //// 2017/02 error
break;
}
if (this.coveredBars[nextIdx]) {
continue;
}
var nextBar = harmonicStream.HarmonicBars.ElementAt(nextIdx);
if (nextBar == null) {
continue;
}
//// Test if next repetition exists
var existsRepeatedMotive = string.CompareOrdinal(harmonicBar.UniqueIdentifier, nextBar.UniqueIdentifier) == 0
&& harmonicStream.EqualSegments(barIdx + 1, nextIdx + 1, minMotiveLength - 1);
if (!existsRepeatedMotive) {
continue;
}
//// Determine Length of the repeated motive
var lengthOfMotive = harmonicStream.LengthOfMotive(barIdx, nextIdx);
lengthOfMotive = Math.Min(lengthOfMotive, nextIdx - barIdx);
this.harMotiveNumber++;
var harMotive = HarmonicMotive.GetNewHarmonicMotive(harmonicStream.Header, this.harMotiveNumber);
harmonicStream.WriteToMotive(harMotive, barIdx, lengthOfMotive); //// , 1
var change = new HarmonicChange(barIdx + 1) { HarmonicMotive = harMotive };
//// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel;
for (var b = barIdx; b < barIdx + lengthOfMotive; b++) {
if (b < this.coveredBars.Length) {
this.coveredBars[b] = true;
}
}
//// break;
return change;
}
}
return null;
}
///
/// Occupies the stream by harmonic motive.
///
/// The harmonic stream.
/// The given change.
/// Returns value.
private IEnumerable OccupyStreamByHarmonicMotive(HarmonicStream harmonicStream, HarmonicChange givenChange)
{
Contract.Requires(harmonicStream != null);
Contract.Requires(givenChange != null);
var harmonicChanges = new Collection { givenChange };
var numberOfBars = harmonicStream.HarmonicBars.Count;
var barIdx = givenChange.BarNumber - 1;
var harmonicBar = harmonicStream.HarmonicBars.ElementAt(barIdx);
if (harmonicBar == null) {
return harmonicChanges;
}
var nextBars = Math.Min(numberOfBars, this.coveredBars.Count);
for (var nextIdx = barIdx + 1; nextIdx < nextBars; nextIdx++) {
if (this.coveredBars[nextIdx]) {
continue;
}
var nextBar = harmonicStream.HarmonicBars.ElementAt(nextIdx);
if (nextBar == null) {
continue;
}
//// Test if next repetition exists
var existsRepeatedMotive = string.CompareOrdinal(harmonicBar.UniqueIdentifier, nextBar.UniqueIdentifier) == 0
&& harmonicStream.EqualSegments(barIdx + 1, nextIdx + 1, givenChange.HarmonicMotive.Length - 1);
if (!existsRepeatedMotive) {
continue;
}
var change = new HarmonicChange(nextIdx + 1) { HarmonicMotive = givenChange.HarmonicMotive };
//// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel;
harmonicChanges.Add(change);
for (var b = nextIdx; b < nextIdx + givenChange.HarmonicMotive.Length; b++) {
if (b < this.coveredBars.Length) {
this.coveredBars[b] = true;
}
}
}
return harmonicChanges;
}
///
/// Extract Harmonical Motive.
///
/// The harmonic stream.
///
/// Returns value.
///
private IEnumerable ExtractHarmonicalMotive(HarmonicStream harmonicStream)
{ //// cyclomatic complexity 10:15
var numberOfBars = this.musicalBody.Context.Header.NumberOfBars;
this.harMotiveNumber = 0;
this.coveredBars = new BitArray(numberOfBars);
var harmonicChanges = new List();
var finished = false;
HarmonicChange change;
while (!finished) {
change = this.FindRepeatedHarmonicMotive(harmonicStream);
if (change != null) {
this.HarmonicModel.AddMotive(change.HarmonicMotive);
var changes = this.OccupyStreamByHarmonicMotive(harmonicStream, change);
harmonicChanges.AddRange(changes);
}
else {
finished = true;
}
}
for (var barIdx = 0; barIdx < numberOfBars; barIdx++) {
if (this.coveredBars[barIdx]) {
continue;
}
var nextIdx = barIdx;
while (nextIdx < numberOfBars && !this.coveredBars[nextIdx]) {
this.coveredBars[nextIdx] = true;
nextIdx++;
}
var lengthOfMotive = nextIdx - barIdx;
this.harMotiveNumber++;
var harMotive = HarmonicMotive.GetNewHarmonicMotive(harmonicStream.Header, this.harMotiveNumber);
harmonicStream.WriteToMotive(harMotive, barIdx, lengthOfMotive); //// , 1
this.HarmonicModel.AddMotive(harMotive);
change = new HarmonicChange(barIdx + 1) { HarmonicMotive = harMotive };
//// change.BlockModel = this.MusicalBlock.SourceMusicalBlockModel;
harmonicChanges.Add(change);
}
return new Collection(harmonicChanges);
}
#endregion
}
}